import { FlowPredicate, ValuePredicate } from "~/core/dsl/Predicates";
import { AssignStmt, BinaryOperator, Constant, InvokeExpr, InvokeStmt, Variable, FieldReference, Statement, IfStmt, StringType, FunctionType } from "qvog-types";
import { pattern } from '~/core/dsl/fluent/QueryDescriptor';
import { DataFlow, DfgTraverse, HamiltonFlow, TaintFlow } from "~/lib/flow";

export const FindLocalStoragePassword = pattern(
    "Find LocalStorage Password", q => q
        .from(f => f
            .withData(v => v.stream().any(s =>
                (s instanceof InvokeExpr) && (
                    (s.target === "setItem") &&
                    (base => base && base instanceof Variable ?
                        base.name === 'localStorage' : false)(s.base)
                ) &&
                (arg0 => arg0 instanceof Constant && arg0.type.name === "string" ?
                    ["password", "pwd", "pw", "passwd", "pass", "passw", "pswd"].includes(arg0.value) : false)(s.args[0])
            ))
            .as("Warning"))
        .select("Warning")
);

export const FindLocalStoragePasswordThroughDataFlow = pattern(
    "Find LocalStorage Password Data Flow", q => q
        .from(f => f.withData(v => v.stream().any(s =>
            s instanceof AssignStmt && ((value => value instanceof Variable &&
                value.name === "localStorage")(s.value) || (
                    (value => value instanceof FieldReference ? value.name === "localStorage" &&
                        (base => base instanceof Variable ? base.name === "window" : false)(value.base) : false
                    )(s.value)
                ))
        )).as("Source"))
        .from(f => f.withPredicate(s => {
            if (s instanceof InvokeStmt) {
                let expr: InvokeExpr = s.expression;
                return expr.target === "setItem" && (arg0 => arg0 instanceof Constant && arg0.type.name === "string" ?
                    ["password", "pwd", "pw", "passwd", "pass", "passw", "pswd"].includes(arg0.value) : false)(expr.args[0])
            }
            return false;
        }).as("Sink"))
        .exists((f) => f.configure({
            flow: new HamiltonFlow(),
            strategy: new DfgTraverse()
        }).source("Source").sink("Sink").as("DataFlow"), () => new DataFlow())
        .select("Source", "Sink", "DataFlow")
);

export const FindMathRandom = pattern(
    "Find Math Random", q => q
        .from(f => f
            .withData(v => v.stream().any(s =>
                (s instanceof InvokeExpr) && s.target === "random" &&
                (base => base instanceof Variable ? base.name === 'Math' : false)(s.base)
            )).as("Warning"))
        .select("Warning")
);

export const FindLogSensitiveInfo = pattern(
    "Find Log Sensitive Info", q => q
        .from(f => f
            .withData(v => v.stream().any(s =>
                (s instanceof AssignStmt) && (target => target instanceof Variable ? target.name === 'cardNumber' || target.name === 'cvv' : false)(s.target)
            )).as("Source"))
        .from(f => f.withPredicate(v => v.stream().any(s =>
            s instanceof InvokeExpr && s.target === "log" && (base => base instanceof Variable ? base.name == "console" : false)(s.base))).as("Sink"))
        .exists(f => f.configure({
            flow: new HamiltonFlow(),
            strategy: new DfgTraverse()
        }).source("Source").sink("Sink").as("DataFlow"), () => new DataFlow())
        .select("Source", "Sink", "DataFlow")
);

export const FindUnsafeRedirect = pattern(
    "Find Unsafe Redirect", q => q
        .from(f => f.withData(v => v.stream().any(s => s instanceof FieldReference &&
            s.name === "url")).as("Source"))
        .from(f => f.withPredicate(s => s instanceof InvokeStmt &&
            (expr => expr instanceof InvokeExpr && expr.target === "redirect")(s.expression)).as("Sink"))
        .from(f => f.withPredicate(s => s instanceof IfStmt).as("Barrier"))
        .exists(f => f.source("Source").barrier("Barrier").sink("Sink").as("TaintFlow"), () => new TaintFlow())
        .select("Source", "Sink", "TaintFlow")
);

export const FindXSS = pattern(
    "Find XSS", q => q
        .from(f => f.withData(v => v.stream().any(s =>
            s instanceof AssignStmt && (value => value instanceof Constant && value.type.name === "string" && value.value.match(/<script>.*<\/script>/) !== null
            )(s.value)
        )).as("Source"))
        .from(f => f.withPredicate(s => s instanceof AssignStmt &&
            (target => target instanceof FieldReference && target.name === "innerHTML")(s.target)).as("Sink"))
        .from(f => f.withPredicate(v => v.stream().any(s =>
            s instanceof InvokeExpr && s.target === "sanitize")).as("Barrier"))
        .exists(f => f.source("Source").barrier("Barrier").sink("Sink").as("TaintFlow"), () => new TaintFlow())
        .select("Source", "Sink", "TaintFlow")
);

// export const FindInfiniteLoop = pattern(
//     "Find Infinite Loop", q => q
//         .from(f => f.withData(v => v.stream().any(s =>
//             (s instanceof Statement) &&
//             (
//                 /while\s*\(\s*(true|1==1|1)\s*\)/.test(s.getCode()) ||
//                 /for\s*\(\s*;\s*(true|1==1|1)?\s*;\s*\)/.test(s.getCode()) ||
//                 /do\s*\{[\s\S]*?\}\s*while\s*\(\s*(true|1==1|1)\s*\)/.test(s.getCode())
//             )
//         )).as("InfiniteLoop"))
//         .select("InfiniteLoop")
// );

export const FindInsecureHttpRequest = pattern(
    "Find Insecure HTTP Request", q => q
        //XMLHttpRequest、axios
        .from(f => f.withData(v => v.stream().any(s =>
        ((s instanceof InvokeExpr) && (() => {
            const target = s.target;
            const base = s.base;
            const arg0 = s.args[0];
            const arg1 = s.args[1];

            //ts: fetch(); ets: new http.HttpClient().request(), dataAbility.query()
            const isFetch = ["fetch", "request", "query"].includes(target) &&
                arg0 instanceof Constant;

            const isAxios = (base instanceof Variable) &&
                base.name === "axios" &&
                ["get", "post", "put", "delete"].includes(target) &&
                arg0 instanceof Constant;

            const isXHR = (base instanceof Variable) &&
                base.type.name.includes("XMLHttpRequest") &&
                //console.log(base?.type.name);
                target === "open" &&
                s.args[1] instanceof Constant;

            let url;
            if (isAxios || isFetch) {
                url = arg0 instanceof Constant ? arg0.value : "";
            }
            else {
                url = arg1 instanceof Constant ? arg1.value : "";
            }
            const isSecure = url.startsWith("https://");
            return (isAxios || isXHR || isFetch) && !isSecure;
        })())
        )).as("InsecureHttpRequest"))
        .select("InsecureHttpRequest")
);

export const FindInsecureHttpRequestThroughDataflow = pattern(
    "Find Insecure HTTP Request Data Flow", q => q
        .from(f => f.withData(v => v.stream().any(s =>
            s instanceof Constant &&
            s.type.name === "string" &&
            s.value.startsWith("http://")
        )).as("Source"))

        .from(f => f.withPredicate(s => {
                if (!(s instanceof InvokeStmt)) return false;
                const expr = s.expression;
                const isFetch = ["fetch", "request", "query"].includes(expr.target) &&
                    expr.argCount > 0 &&
                    expr.args[0] instanceof Variable;

                const isAxios = (expr.base instanceof Variable) &&
                    (() => {
                        const base = expr.base as Variable;
                        return base.name === "axios";
                    })() &&
                    ["get", "post", "put", "delete"].includes(expr.target) &&
                    expr.argCount > 0 &&
                    expr.args[0] instanceof Variable;

                const isXHR = expr.base instanceof Variable &&
                    expr.base?.type.name.includes("XMLHttpRequest") &&
                    expr.target === "open" &&
                    expr.argCount > 1 &&
                    expr.args[1] instanceof Variable;
                if (isFetch || isAxios || isXHR)
                    return true;
                return false;
            }).as("Sink"))
    .exists(f => f.configure({
        flow: new HamiltonFlow(),
        strategy: new DfgTraverse()
    }).source("Source").sink("Sink").as("DataFlow"), () => new DataFlow())
    .select("Source", "Sink", "DataFlow")
);

// export const FindCSRFVulnerability = pattern(
//     "Find CSRF Vulnerability", q => q
//         .from(f => f.withData(v => v.stream().any(s =>
//             s instanceof InvokeStmt &&
//             (expr => {
//                 if (!(expr instanceof InvokeExpr)) return false;

//                 const isFetch = expr.target === "fetch";
//                 const arg0 = expr.args[0];
//                 const arg1 = expr.args[1];
//                 if (isFetch && arg1 instanceof Variable) {
//                     console.log(arg1);
//                     //console.log(expr.target);
//                     //console.log(expr.argCount);
//                 }

//                 //console.log(expr.args[1].getCode()); 
//                 //const hasUnsafeMethod = !!(arg1 && arg1.getCode().match(/method\s*:\s*['"](POST|PUT|DELETE)['"]/));

//                 return isFetch && arg0 instanceof Constant;
//                 //&& hasUnsafeMethod;
//             })(s.expression)
//         )).as("CSRFSource"))
//         // .from(f => f.withPredicate(s => {
//         //     if (!(s instanceof InvokeStmt)) return false;

//         //     const expr = s.expression as InvokeExpr;
//         //     const arg1 = expr.args[1];
//         //     const hasHeaders = arg1 && !!arg1.getCode().match(/headers\s*:\s*\{/);
//         //     const hasCSRFToken = arg1 && !!arg1.getCode().match(/['"]CSRF-Token['"]/);

//         //     return hasHeaders && !hasCSRFToken;
//         // }).as("CSRFVulnerability"))

//         // .where(f => f.configure({
//         //     flow: new HamiltonFlow(),
//         //     strategy: new DfgTraverse()
//         // }).source("CSRFSource").sink("CSRFVulnerability").as("UnprotectedFlow"))
//         //.select(["CSRFSource", "CSRFVulnerability", "UnprotectedFlow"])
//         .select(["CSRFSource"])
// );

export const FindExcessiveSQLPrivileges = pattern(
    "Find Excessive SQL Privileges", q => q
        .from(f => f.withData(v => v.stream().any(s =>
            s instanceof AssignStmt && (value => {
                return value instanceof Constant &&
                    value.type.name === "string" &&
                    /GRANT\s+ALL\s+PRIVILEGES/i.test(value.value) &&
                    /ON\s+.*\*.*\s+TO/i.test(value.value);
            })(s.value)
        )).as("Source"))

        .from(f => f.withPredicate(s => {
            if (!(s instanceof InvokeStmt)) return false;

            const expr = s.expression as InvokeExpr;
            const target = expr.target;
            const arg0 = expr.args[0];
            const isSQLExecute = ["execute", "query", "run"].includes(target);

            return isSQLExecute && (arg0 instanceof Variable || arg0 instanceof Constant);
        }).as("Sink"))

        .exists(f => f.configure({
            flow: new HamiltonFlow(),
            strategy: new DfgTraverse()
        }).source("Source").sink("Sink").as("DataFlow"), () => new DataFlow())
        .select("Source", "Sink", "DataFlow")
);

export const FindUnsafeFilePath = pattern(
    "Find Unsafe File Path", q => q
        .from(f => f.withData(v => v.stream().any(s =>
            s instanceof Constant && (s.value.includes('..') || s.value.startsWith('/'))
        )).as("Source"))

        .from(f => f.withPredicate(s => {
            if (!(s instanceof InvokeStmt)) return false;

            const expr = s.expression as InvokeExpr;
            const target = expr.target;
            // const arg0 = expr.args[0];
            const isFileOperate = ["openSync", "writeSync", "readSync", "open", "write", "read"].includes(target);

            return isFileOperate;
            // return isFileOperate && (arg0 instanceof Variable || arg0 instanceof Constant);
        }).as("Sink"))

        .exists(f => f.configure({
            flow: new HamiltonFlow(),
            strategy: new DfgTraverse()
        }).source("Source").sink("Sink").as("DataFlow"), () => new DataFlow())
        .select("Source", "Sink", "DataFlow")
);

export const FindUnsafeMacHash = pattern(
    "Find Unsafe Mac Hash", q => q
        .from(f => f
            .withData(v => v.stream().any(s =>
                (s instanceof InvokeExpr) && s.target === "createMac" &&
                (base => base instanceof Variable ? base.name === 'cryptoFramework' : false)(s.base)
            )).as("Warning"))
        .select("Warning")
);
